package com.obas.flex.common.util {

   import mx.collections.ArrayCollection;
   import mx.collections.CursorBookmark;
   import mx.collections.IViewCursor;

   public class ListDataUtil {
      public function ListDataUtil(keyFunc:Function) {
         _keyFunc = keyFunc;
      }

      private var _keyFunc:Function;

      private var selectedItems:Array;
      private var existingIndex:Number = -1;
      private var newItem:*;

      private function filterSelectedItems(item:*, index:int, array:Array):Boolean {
         for each (var selectedItem:* in selectedItems) {
            if (_keyFunc(selectedItem) == _keyFunc(item)) {
               return true;
            }
         }
         return false;
      }

      public function getSelectedItems(items:Array, source:ArrayCollection):Array {
         selectedItems = items;
         return source.source.filter(filterSelectedItems);
      }

      // O(n).
      public function addOrUpdateOrderedItems(src:ArrayCollection, dest:ArrayCollection, compare:Function):void {
         var srcCursor:IViewCursor = src.createCursor();
         var dstCursor:IViewCursor = dest.createCursor();
         var srcItem:* = srcCursor.current;
         var desItem:* = dstCursor.current;

         while (!srcCursor.afterLast) {
            srcItem = srcCursor.current;
            desItem = dstCursor.current;

            while (compare(srcItem, desItem) == -1 && !dstCursor.afterLast) {// mean smaller.
               // sync header
               srcCursor.insert(desItem);

               dstCursor.moveNext();
               desItem = dstCursor.current;
            }

            if (dstCursor.afterLast) {
               while (!srcCursor.afterLast) {
                  srcCursor.remove();
               }
            } else {
               var stCursor:IViewCursor = srcCursor;
               var dtCursor:IViewCursor = dstCursor;
               // update source item.
               while (compare(srcItem, desItem) == 0 && !dtCursor.afterLast) {// mean equal.
                  if (_keyFunc(srcItem) == _keyFunc(desItem)) {
                     src.setItemAt(desItem, src.getItemIndex(srcItem));
                     srcCursor.moveNext();
                     dtCursor.remove();
                     break;
                  } else {
                     dtCursor.moveNext();
                     desItem = dtCursor.current;
                  }
               }

               if (dtCursor.afterLast || compare(srcItem, desItem) == 1) {
                  if (!srcCursor.afterLast && !srcCursor.beforeFirst) {
                     srcCursor.remove();
                  }
               }
            }
         }
      }

      public function addOrUpdateItems(src:Array, dest:Array, keepOldItem:Boolean = false):void {
         var map:Object = {};
         var key:Object;

         var srcLen:int = src.length;
         for (var itr:int = 0; itr < srcLen; itr++) {
            key = _keyFunc(src[itr]);
            map[key] = new Index(src[itr], 0x0001);
         }

         var destLen:int = dest.length;
         for (itr = 0; itr < destLen; itr++) {
            key = _keyFunc(dest[itr]);

            if (map[key] == null) {// new index.
               map[key] = new Index(dest[itr], 0x0010);
            } else {
               (map[key] as Index).mask = 0x0011;
            }
         }

         // delete
         if (!keepOldItem) {
            var index:Index;
            for (itr = srcLen - 1; itr >= 0; itr--) {
               var srcItem:* = src[itr];
               key = _keyFunc(srcItem);
               index = map[key];

               if (index == null) {
                  continue;
               }

               if (index.mask == 0x0001) {// only exists in source array.
                  src.splice(itr, 1);
                  delete map[key];
               }
            }
         }

         // add
         for (itr = destLen - 1; itr >= 0; itr--) {
            var item:* = dest[itr];

            key = _keyFunc(item);
            index = map[key];

            if (index == null) {
               continue;
            }

            if (index.mask == 0x0010) {// only exists in destination array.
               src.unshift(item);
            } else if (index.mask == 0x0011) {// update related value.
               src.splice(src.indexOf(index.item), 1, item);
            }

            delete map[key];
         }
      }

      private function noExisting(item:*, index:int, array:Array):Boolean {
         if (_keyFunc(newItem) == _keyFunc(item)) {
            existingIndex = index;
         }

         return _keyFunc(newItem) != _keyFunc(item);
      }

      public function addOrUpdateItem(value:*, source:ArrayCollection):void {
         existingIndex = -1;
         newItem = value;

         source.source.every(noExisting);

         if (existingIndex != -1) {
            source.setItemAt(value, existingIndex);
         } else {
            source.addItem(value);
         }
      }

      public function removeItems(src:Array, dest:Array):void {
         var map:Object = {};
         var key:Object;

         var destLen:int = dest.length;
         for (var itr:int = 0; itr < destLen; itr++) {
            key = _keyFunc(dest[itr]);
            map[key] = new Index(dest[itr], 0x0001);
         }

         var index:Index;
         var srcLen:int = src.length;
         for (itr = srcLen - 1; itr >= 0; itr--) {
            var srcItem:* = src[itr];
            key = _keyFunc(srcItem);
            index = map[key];

            if (index == null) {
               continue;
            }

            src.splice(itr, 1);
            delete map[key];
         }
      }
   }
}

class Index {
   public function Index(item:*, mask:int) {
      this.item = item;
      this.mask = mask;
   }

   public var item:*;
   public var mask:int;
}
